// Copyright 2024 Vadim Kharitonovich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"bufio"
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
)

func init() {
	providers = map[ProviderName]Provider{
		HTTPGet: &httpGet{},
	}
}

type ProviderName string

const (
	HTTPGet = "http-get"
)

type Config struct {
	Items [][]byte
}

var (
	providers map[ProviderName]Provider
)

type Provider interface {
	Load(ctx context.Context, source *url.URL) ([]byte, error)
}

type httpGet struct {
}

func (r *httpGet) Load(ctx context.Context, source *url.URL) ([]byte, error) {
	rsp, err := http.Get(source.String())
	if err != nil {
		return nil, fmt.Errorf("http get: %w", err)
	}

	if rsp.StatusCode == http.StatusNotFound {
		return nil, errors.New("http get: resource not found")
	}

	if rsp.StatusCode == http.StatusForbidden {
		return nil, errors.New("http get: access denied")
	}

	if rsp.StatusCode == http.StatusUnauthorized {
		return nil, errors.New("http get: unauthorized")
	}

	if rsp.StatusCode > http.StatusIMUsed {
		return nil, fmt.Errorf("http get: unexpected status code: status_code(%d)", rsp.StatusCode)
	}

	if rsp.Body == nil {
		return nil, nil
	}

	defer rsp.Body.Close()

	body, err := io.ReadAll(rsp.Body)
	if err != nil {
		return nil, fmt.Errorf("http get: io read all: %w", err)
	}

	return body, nil
}

func Load(ctx context.Context, providerName ProviderName, source *url.URL) (*Config, error) {
	provider, exists := providers[providerName]
	if !exists {
		return nil, errors.New("provider not found")
	}

	data, err := provider.Load(ctx, source)
	if err != nil {
		return nil, fmt.Errorf("provider load: %w", err)
	}

	return Parse(ctx, data)
}

func Parse(ctx context.Context, data []byte) (*Config, error) {
	var (
		config = Config{
			Items: make([][]byte, 0, 100),
		}
		scanner = bufio.NewScanner(bytes.NewReader(data))
	)

	for scanner.Scan() {
		if ctx.Err() != nil {
			return nil, ctx.Err()
		}

		item := make([]byte, len(scanner.Bytes()))
		copy(item, scanner.Bytes())
		config.Items = append(config.Items, item)
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("scanner err: %w", err)
	}

	return &config, nil
}
